#!/usr/bin/env python
#
# Mechanisms for producing simulator grid map files, to be read by the
# map_format_reader core module.
#
# Author:   Mike Murphy <mamurph@cs.clemson.edu>
# Revision: 23 September 2009
#
#   Copyright 2009 Clemson University
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#
# This material is based upon work supported under a National Science
# Foundation Graduate Research Fellowship. Any opinions, findings,
# conclusions or recommendations expressed in this publication are those of
# the author(s) and do not necessarily reflect the views of the National
# Science Foundation.


'''
Implements a uniform format writer for grid map data, which will be readable
by the corresponding simvoc.core.map_format_reader module. This plain-text
format is documented externally.
'''

from common_writer import CommonWriter

# Be sure to increment this number when changing the output format!
MAP_FORMAT = 0.90
HEADER = 'grid map generated by map_format_writer'

class MapWriter(CommonWriter):
   '''
   Mechanism for writing dynamic grid map files readable by the core
   map_format_reader module. Writing map entries is a multi-step process,
   which usually begins with a call to write_time() to set the timestamp of
   the current map change. Maps are then produced by calls to add_ce(),
   remove_ce(), grow_cluster(), and shrink_cluster(). New clusters are
   automatically created when grow_cluster() is called with a name not yet
   known to the grid. Similarly, schedulers are added whenever add_ce
   specifies a scheduler not yet in use.

   The grid map is NOT buffered in RAM, which should allow for the creation
   of large map files without swapping. However, the portion of the grid
   that is actually in use for simulation purposes must be loaded into RAM,
   which will limit maximum simulation size.
   '''
   def __init__(self, fh, normalize_time=False):
      '''
      @param fh: Open file handle (w mode) to which map will be written
      @param normalize_time: if True, all timestamps will be normalized
                             relative to the first timestamp written
      @type normalize_time: bool
      '''
      CommonWriter.__init__(self, fh, MAP_FORMAT, HEADER, normalize_time)
   def add_ce(self, ce_name, cluster_name, sched_name):
      '''
      Adds a Compute Element to the grid, which will result in the creation
      of a Site internally. The specified cluster must exist prior to adding
      the CE, otherwise a simulation run-time error will occur. In addition,
      CE names MUST be unique across a single simulation.

      @param ce_name: Name of the CE to add
      @type ce_name: str
      @param cluster_name: Name of the cluster providing machines to this CE
      @type cluster_name: str
      @param sched_name: Name of the scheduler to which this CE will send jobs.
                         If the scheduler does not yet exist, it will be
                         created using the name translator specified in the
                         simulation driver.
      @type sched_name: str
      '''
      self.fh.write('add %s %s %s\n' % (ce_name, cluster_name, sched_name))
   def remove_ce(self, ce_name):
      '''
      Removes a CE from the grid. All jobs running on all schedulers associated
      with the CE will be killed at the time the CE is removed.

      @param ce_name: Computing Element (site) name to remove from the grid
      @type ce_name: str
      '''
      self.fh.write('del %s\n' % ce_name)
   def grow_cluster(self, cluster_name, cpu_count, profile_name=''):
      '''
      Adds machines to a cluster. If the cluster does not exist, it will be
      created automatically. Cluster names MUST be unique across the
      simulation, or errors will occur. An optional profile name allows
      experiments to use different types or configurations of machines in
      simulation runs.

      @param cluster_name: Name of cluster to create or enlarge
      @type cluster_name: str
      @param cpu_count: Number of CPU cores to add
      @type cpu_count: int
      @param profile_name: Optional machine profile name
      @type profile_name: str
      '''
      self.fh.write('grow %s %d' % (cluster_name, cpu_count))
      if profile_name != '':
         self.fh.write(' %s' % profile_name)
      self.fh.write('\n')
   def shrink_cluster(self, cluster_name, cpu_count):
      '''
      Removes machines from a cluster. An error will occur at simulation time
      if a negative number of machines is reached, but it is permissible to
      reduce a cluster to zero size. When shrinking a cluster, any available
      idle nodes will be removed first. If the specified number of CPU cores
      exceeds the idle count, other nodes will be selected randomly (using the
      simulator's random source) for removal. Jobs running on the selected
      nodes will be killed immediately.

      @param cluster_name: Name of cluster to shrink
      @type cluster_name: str
      @param cpu_count: Number of CPU cores to remove
      @type cpu_count: int
      '''
      self.fh.write('shrink %s %d\n' % (cluster_name, cpu_count))
#
